﻿using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Xml.Serialization;

namespace ALiOssNet6;

public class Oss : IOss
{
    private readonly OssConfig _Config = new OssConfig();
    private readonly ILogger _Logger;
    private readonly IServiceProvider _ServiceProvider;
    public Oss(IConfiguration config, ILogger<Oss> logger, IServiceProvider serviceProvider)
    {
        _ServiceProvider = serviceProvider;
        config.GetSection(OssConfig.SectionName).Bind(_Config);
        _Logger = logger;
    }
    #region Static Method
    private static string SignHMACSHA1(byte[] key, string text)
    {
        using HMACSHA1 hmacsha1 = new HMACSHA1(key);
        return Convert.ToBase64String(hmacsha1.ComputeHash(Encoding.UTF8.GetBytes(text)));
    }
    private static byte[] GetMD5Bytes(byte[] bytes)
    {
        using MD5 md5 = MD5.Create();
        return md5.ComputeHash(bytes);
    }

    private static string XmlSer(object o)
    {
        XmlSerializer ser = new XmlSerializer(o.GetType());
        XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
        ns.Add("", "");
        using MemoryStream ms = new MemoryStream();
        ser.Serialize(ms, o, ns);
        return Encoding.UTF8.GetString(ms.ToArray());
    }
    private static T? XmlDeser<T>(string xml) where T : class
    {
        XmlSerializer ser = new XmlSerializer(typeof(T));
        using MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(xml));
        return ser.Deserialize(ms) as T;
    }
    #endregion

    public async Task<bool> IsExistedAsync(string bucketName, string filename)
    {
        var dt = DateTime.Now.ToUniversalTime();
        var date = dt.ToString("r");

        string stringToSign = $"HEAD\n\n\n{date}\n/{bucketName}/{filename}?objectMeta";
        string sign = SignHMACSHA1(_Config.KeyBytes, stringToSign);



        try
        {
            var req = new HttpRequestMessage(HttpMethod.Head, $"http://{bucketName}.{_Config.EndPoint}/{filename}?objectMeta");
            req.Headers.Host = $"{bucketName}.{_Config.EndPoint}";
            req.Headers.Authorization = new AuthenticationHeaderValue("OSS", $"{_Config.AccessKeyId}:{sign}");
            req.Headers.Date = dt;
            using var scopre = _ServiceProvider.CreateScope();
            var hc = scopre.ServiceProvider.GetRequiredService<HttpClient>();
            var r = await hc.SendAsync(req);
            return r.StatusCode == System.Net.HttpStatusCode.OK;
        }
        catch (Exception ex)
        {
            _Logger.LogError($"{nameof(IsExistedAsync)} Exception:{ex}");
            return false;
        }
    }



    public async Task<bool> SaveAsync(string bucketName, string filename, Stream stream)
    {
        var dt = DateTime.Now.ToUniversalTime();
        var date = dt.ToString("r");
        var sign = SignHMACSHA1(_Config.KeyBytes, $"PUT\n\napplication/octet-stream\n{date}\n/{bucketName}/{filename}");

        StreamContent streamContent = new StreamContent(stream);
        streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");

        var req = new HttpRequestMessage(HttpMethod.Put, $"http://{bucketName}.{_Config.EndPoint}/{filename}");
        req.Content = streamContent;
        req.Headers.Host = $"{bucketName}.{_Config.EndPoint}";
        req.Headers.Authorization = new AuthenticationHeaderValue("OSS", $"{_Config.AccessKeyId}:{sign}");
        req.Headers.Date = dt;

        try
        {
            using var scopre = _ServiceProvider.CreateScope();
            var hc = scopre.ServiceProvider.GetRequiredService<HttpClient>();

            var r = await hc.SendAsync(req);
            if (!r.IsSuccessStatusCode)
            {
                _Logger.LogWarning($"{nameof(SaveAsync)}:{await r.Content.ReadAsStringAsync()}");
            }
            return r.IsSuccessStatusCode;

        }
        catch (Exception ex)
        {
            _Logger.LogError($"{nameof(SaveAsync)}:{ex}");
            return false;
        }
    }
    public async Task<bool> SavePublicReadAsync(string bucketName, string filename, Stream stream)
    {
        var dt = DateTime.Now.ToUniversalTime();
        var date = dt.ToString("r");

        var sign = SignHMACSHA1(_Config.KeyBytes, $"PUT\n\napplication/octet-stream\n{date}\nx-oss-object-acl:public-read\n/{bucketName}/{filename}");

        StreamContent streamContent = new StreamContent(stream);
        streamContent.Headers.Add("x-oss-object-acl", "public-read");
        streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");

        var req = new HttpRequestMessage(HttpMethod.Put, $"http://{bucketName}.{_Config.EndPoint}/{filename}");
        req.Content = streamContent;
        req.Headers.Host = $"{bucketName}.{_Config.EndPoint}";
        req.Headers.Authorization = new AuthenticationHeaderValue("OSS", $"{_Config.AccessKeyId}:{sign}");
        req.Headers.Date = dt;
        try
        {
            using var scopre = _ServiceProvider.CreateScope();
            var hc = scopre.ServiceProvider.GetRequiredService<HttpClient>();

            var r = await hc.SendAsync(req);
            if (!r.IsSuccessStatusCode)
                _Logger.LogWarning($"{nameof(SavePublicReadAsync)}:{await r.Content.ReadAsStringAsync()}");
            return r.IsSuccessStatusCode;
        }
        catch (Exception ex)
        {
            _Logger.LogError($"{nameof(SavePublicReadAsync)}:{ex}");
            return false;
        }
    }

    public async Task<FileMetas?> GetMetasAsync(string bucketName, string directoryName = "", string delimiter = "")
    {
        string resource = "?list-type=2&max-keys=1000";
        if (!string.IsNullOrEmpty(directoryName))
            resource += $"&prefix={directoryName}/";
        if (!string.IsNullOrEmpty(delimiter))
            resource += $"&delimiter={delimiter}";

        var dt = DateTime.Now.ToUniversalTime();
        var date = dt.ToString("r");

        string stringToSign = $"GET\n\n\n{date}\n/{bucketName}/";
        string sign = SignHMACSHA1(_Config.KeyBytes, stringToSign);


        var req = new HttpRequestMessage(HttpMethod.Get, $"http://{bucketName}.{_Config.EndPoint}{resource}");
        req.Headers.Host = $"{bucketName}.{_Config.EndPoint}";
        req.Headers.Authorization = new AuthenticationHeaderValue("OSS", $"{_Config.AccessKeyId}:{sign}");
        req.Headers.Date = dt;

        try
        {
            using var scopre = _ServiceProvider.CreateScope();
            var hc = scopre.ServiceProvider.GetRequiredService<HttpClient>();
            var r = await hc.SendAsync(req);

            var str = await r.Content.ReadAsStringAsync();
            if (!r.IsSuccessStatusCode)
            {
                _Logger.LogWarning($"{nameof(GetMetasAsync)}:{str}");
                return default;
            }
            return XmlDeser<FileMetas>(str);
        }
        catch (Exception ex)
        {
            _Logger.LogError($"{nameof(GetMetasAsync)}:{ex}");
            return default;
        }
    }

    public async Task<byte[]?> GetAsync(string bucketName, string filename)
    {

        var dt = DateTime.Now.ToUniversalTime();
        var date = dt.ToString("r");

        var sign = SignHMACSHA1(_Config.KeyBytes, $"GET\n\n\n{date}\n/{bucketName}/{filename}");

        var req = new HttpRequestMessage(HttpMethod.Get, $"http://{bucketName}.{_Config.EndPoint}/{filename}");
        req.Headers.Host = $"{bucketName}.{_Config.EndPoint}";
        req.Headers.Authorization = new AuthenticationHeaderValue("OSS", $"{_Config.AccessKeyId}:{sign}");
        req.Headers.Date = dt;
        try
        {

            using var scopre = _ServiceProvider.CreateScope();
            var hc = scopre.ServiceProvider.GetRequiredService<HttpClient>();
            var r = await hc.SendAsync(req);
            if (!r.IsSuccessStatusCode)
            {
                _Logger.LogWarning($"{nameof(GetAsync)}:{await r.Content.ReadAsStringAsync()}");

                return default;
            }
            else
                return await r.Content.ReadAsByteArrayAsync();
        }
        catch (Exception ex)
        {
            _Logger.LogError($"{nameof(GetAsync)}:{ex}");
            return default;
        }
    }

    public async Task<bool> DeleteAsync(string bucketName, string filename)
    {

        var dt = DateTime.Now.ToUniversalTime();
        var date = dt.ToString("r");
        var sign = SignHMACSHA1(_Config.KeyBytes, $"DELETE\n\n\n{date}\n/{bucketName}/{filename}");

        var req = new HttpRequestMessage(HttpMethod.Delete, $"http://{bucketName}.{_Config.EndPoint}/{filename}");
        req.Headers.Host = $"{bucketName}.{_Config.EndPoint}";
        req.Headers.Authorization = new AuthenticationHeaderValue("OSS", $"{_Config.AccessKeyId}:{sign}");
        req.Headers.Date = dt;


        try
        {

            using var scopre = _ServiceProvider.CreateScope();
            var hc = scopre.ServiceProvider.GetRequiredService<HttpClient>();
            var r = await hc.SendAsync(req);
            if (!r.IsSuccessStatusCode)
                _Logger.LogWarning($"{nameof(DeleteAsync)}:{await r.Content.ReadAsStringAsync()}");
            return r.IsSuccessStatusCode;
        }
        catch (Exception ex)
        {
            _Logger.LogError($"{nameof(DeleteAsync)}:{ex}");
            return false;
        }
    }

    public async Task<bool> DeleteAsync(string bucketName, List<string> filenames)
    {

        var dt = DateTime.Now.ToUniversalTime();
        var date = dt.ToString("r");

        var xmlstr = XmlSer(new NeedDeleteFilenames() { Items = filenames.Select(ii => new NeedDeleteFilenames.Item { Key = ii }).ToList() });
        var md5 = GetMD5Bytes(Encoding.UTF8.GetBytes(xmlstr));
        var sign = SignHMACSHA1(_Config.KeyBytes, $"POST\n{Convert.ToBase64String(md5)}\ntext/xml\n{date}\n/{bucketName}/?delete");

        StringContent content = new StringContent(xmlstr);
        content.Headers.ContentType = new MediaTypeHeaderValue("text/xml");
        content.Headers.ContentMD5 = md5;

        var req = new HttpRequestMessage(HttpMethod.Post, $"http://{bucketName}.{_Config.EndPoint}/?delete");
        req.Content = content;
        req.Headers.Host = $"{bucketName}.{_Config.EndPoint}";
        req.Headers.Authorization = new AuthenticationHeaderValue("OSS", $"{_Config.AccessKeyId}:{sign}");
        req.Headers.Date = dt;

        try
        {
            using var scopre = _ServiceProvider.CreateScope();
            var hc = scopre.ServiceProvider.GetRequiredService<HttpClient>();
            var r = await hc.SendAsync(req);
            if (!r.IsSuccessStatusCode)
                _Logger.LogWarning($"{nameof(DeleteAsync)}:{await r.Content.ReadAsStringAsync()}");
            return r.IsSuccessStatusCode;
        }
        catch (Exception ex)
        {
            _Logger.LogError($"{nameof(DeleteAsync)}:{ex}");
            return false;
        }
    }
    public async Task<bool> DeleteBucketAsync(string bucketName)
    {

        var dt = DateTime.Now.ToUniversalTime();
        var date = dt.ToString("r");
        var sign = SignHMACSHA1(_Config.KeyBytes, $"DELETE\n\n\n{date}\n/{bucketName}/");

        var req = new HttpRequestMessage(HttpMethod.Delete, $"http://{bucketName}.{_Config.EndPoint}");

        req.Headers.Host = $"{bucketName}.{_Config.EndPoint}";
        req.Headers.Authorization = new AuthenticationHeaderValue("OSS", $"{_Config.AccessKeyId}:{sign}");
        req.Headers.Date = dt;


        try
        {
            using var scopre = _ServiceProvider.CreateScope();
            var hc = scopre.ServiceProvider.GetRequiredService<HttpClient>();
            var r = await hc.SendAsync(req);
            if (!r.IsSuccessStatusCode)
                _Logger.LogWarning($"{nameof(DeleteBucketAsync)}:{await r.Content.ReadAsStringAsync()}");
            return r.IsSuccessStatusCode;
        }
        catch (Exception ex)
        {
            _Logger.LogError($"{nameof(DeleteBucketAsync)}:{ex}");
            return false;

        }
    }
}


